package org.openzip.openzip.gui.util;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.*;

import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;

import org.openzip.openzip.archiving.gui.ArchiverGUI;
import org.openzip.openzip.gui.util.checkBox.BasicCheckBoxModel;
import org.openzip.openzip.gui.util.checkBox.FileCheckBoxModel;
import org.openzip.openzip.gui.util.checkBox.State;
import org.openzip.openzip.gui.util.fileTree.CheckboxedTree;
import org.openzip.openzip.gui.util.fileTree.FileTreeNode;
import org.openzip.openzip.gui.util.fileTree.event.TreeCheckSelectionEvent;
import org.openzip.openzip.gui.util.fileTree.event.TreeCheckSelectionListener;
import org.openzip.openzip.gui.util.layout.StandardForm;
import org.openzip.openzip.gui.util.selection.FileSelectionObserver;
import org.openzip.openzip.gui.util.selection.SelectedFilesModel;

/**
 * It provides a graphical user interface for the archiving logic. For seeing
 * how the GUI looks like, refer to the specification.<br>
 * <br>
 * This class serves as basic GUI which can be used as skeleton for archiving as
 * well as unpacking. At this time this is just used for archiving gui, although
 * unpacking gui based on this skeleton is also part of this project in class
 * UnpackGUI.<br>
 * Currently this class is used only by archiving logic. Unpacking relies on its
 * own logic.
 * 
 * @author Jakub Závěrka
 */
public abstract class SkeletonGUI {

	protected JFrame mainWindow;

	// Tree with folders in it
	private CheckboxedTree fileTree;
	private JScrollPane fileTreeScrollPane;
	// List with files from selected folder in the tree
	private FileCheckList folderList;
	private JScrollPane folderListScrollPane;
	private Collection<FileCheckBoxModel> currentFolderCheckBoxes;
	// List with all selected files
	private JList fileList;
	private DefaultListModel fileListModel;
	private JScrollPane fileListScrollPane;

	private JLabel filesCountLabel;
	private JButton addFileButton;
	private JButton removeFileButton;
	private JButton closeButton;

	// This holds information about all selected files from all GUI sources
	protected SelectedFilesModel globalSelectionModel;
	protected JButton okButton;
	protected IFilePanel filePanel;

	/**
	 * This will create a new instance of the GUI. Should be called only once.
	 * Preferred method to display the GUI is the <code>showGui</code> method.
	 */
	public SkeletonGUI(String label, String okButtonLabel,
			IFilePanel filePanel, File root, boolean dirsOnly) {
		globalSelectionModel = new SelectedFilesModel();
		globalSelectionModel.registerObserver(new FilesCountObserver());
		prepareComponents(label, okButtonLabel, filePanel, root, dirsOnly);
	}

	/**
	 * This will prepare the graphical components and layout them in their
	 * respective containers.
	 * 
	 * @param dirsOnly
	 */
	private void prepareComponents(String label, String okButtonLabel,
			IFilePanel filePanel, File root, boolean dirsOnly) {
		prepareMainWindow(label);
		// filePanel = new ArchiverFilePanel();
		filePanel.getJComponent().setBorder(
				BorderFactory.createEmptyBorder(0, 0, 5, 0));
		this.filePanel = filePanel;
		StandardForm form = new StandardForm(filePanel.getJComponent(),
				prepareCenterPanel(okButtonLabel, root, dirsOnly), null);
		form.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

		mainWindow.setContentPane(form.getJComponent());
		mainWindow.setSize(700, 600);
		mainWindow.setLocationRelativeTo(null);
	}

	/**
	 * This will prepare the center panel and its components. Gets called by
	 * <code>prepareComponents()</code>.
	 * 
	 * @param filePanel2
	 * @param okButtonLabel
	 * @param dirsOnly
	 */
	private Component prepareCenterPanel(String okButtonLabel, File root,
			boolean dirsOnly) {

		//Node rootNode = new Node();
		
		fileTree = new CheckboxedTree(root, dirsOnly);
		fileTree.setRootVisible(false);
		fileTree.setGlobalSelectionModel(globalSelectionModel);
		fileTree.addTreeSelectionListener(new NodeSelectionListener());
		fileTree.addTreeCheckSelectionListener(new CheckSelectionListener());
		fileTreeScrollPane = new JScrollPane(fileTree);
		fileTreeScrollPane.setPreferredSize(new Dimension(200, 0));

		folderList = new FileCheckList();
		folderList.addMouseListener(new ListCheckMouseListener());
		folderList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		folderListScrollPane = new JScrollPane(folderList);
		folderListScrollPane.setPreferredSize(new Dimension(200, 0));

		Box b = new Box(BoxLayout.X_AXIS);

		JPanel left = new JPanel(new BorderLayout());
		left.add(new JLabel("Folders:"), BorderLayout.NORTH);
		left.add(fileTreeScrollPane);
		b.add(left);

		b.add(Box.createRigidArea(new Dimension(5, 0)));

		JPanel center = new JPanel(new BorderLayout());
		center.add(new JLabel("Files:"), BorderLayout.NORTH);
		center.add(folderListScrollPane);
		b.add(center);

		b.add(Box.createRigidArea(new Dimension(5, 0)));

		JPanel right = new JPanel(new BorderLayout());
		right.add(filesCountLabel = new JLabel("Number of files: 0"),
				BorderLayout.NORTH);
		right.add(prepareListPanel(okButtonLabel));
		b.add(right);

		b.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0));
		return b;
	}

	/**
	 * This will prepare panel with the file list and its components. Gets
	 * called by <code>prepareCenterPanel()</code>.
	 * 
	 * @param okButtonLabel
	 */
	private Component prepareListPanel(String okButtonLabel) {
		fileList = new JList();
		fileList.setModel(fileListModel = new DefaultListModel());
		fileList.setCellRenderer(new FileListCellRenderer());
		fileListScrollPane = new JScrollPane(fileList);
		fileListScrollPane.setPreferredSize(new Dimension(200, 0));
		addFileButton = new JButton("Add a file...");
		addFileButton.addActionListener(new AddFileButtonListener());
		removeFileButton = new JButton("Remove file");
		removeFileButton.addActionListener(new RemoveFileButtonListener());
		closeButton = new JButton("Close");
		closeButton.addActionListener(new CancelListener());
		okButton = new JButton(okButtonLabel);
		okButton.setPreferredSize(new Dimension(130, 50));

		JPanel fileListPanel = new JPanel(new BorderLayout());
		fileListPanel.add(fileListScrollPane);
		if (this instanceof ArchiverGUI) {
			Box fileButtonPanel = new Box(BoxLayout.X_AXIS);
			fileButtonPanel.add(addFileButton);
			fileButtonPanel.add(Box.createRigidArea(new Dimension(5, 0)));
			fileButtonPanel.add(removeFileButton);
			fileButtonPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5,
					0));
			fileListPanel.add(fileButtonPanel, BorderLayout.SOUTH);
		}

		JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
		buttonPanel.add(closeButton);
		buttonPanel.add(Box.createRigidArea(new Dimension(10, 0)));
		buttonPanel.add(okButton);
		buttonPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));

		StandardForm rightForm = new StandardForm(null, fileListPanel,
				buttonPanel);

		return rightForm.getJComponent();
	}

	/**
	 * This will initialize the main window of the interface. gets called by the
	 * <code>prepareComponents()</code> method.
	 * 
	 * @param label
	 */
	private void prepareMainWindow(String label) {
		mainWindow = new JFrame(label);
		mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

	/**
	 * Tree selection listener for the file tree.
	 */
	private class NodeSelectionListener implements TreeSelectionListener 
	{
		/**
		 * Cache of created FileCheckBoxes for loaded folders. If folder is
		 * loaded for the first time, it gets fresh new instances of
		 * <code>FileCheckBox</code> for its files and those instances are
		 * stored in cache.<br>
		 * If file is loaded for the second time, old instances are reused.
		 */
		private Map<File, Collection<FileCheckBoxModel>> cache = 
			new HashMap<File, Collection<FileCheckBoxModel>>();

		/**
		 * This will load all children files of selected folder and display them 
		 * in the folder list for single file selecting.
		 */
		@Override
		public void valueChanged(TreeSelectionEvent e) {
			FileTreeNode node = (FileTreeNode) e.getPath()
					.getLastPathComponent();
			FileCheckBoxModel masterCheckBox = node.getFileCheckBox();
			File folder = masterCheckBox.getFile();
			if (folder.isDirectory()) {

				// This is problem with links, because they show in tree :-(
				// Problem is caused by Java itself - it cannot handle sym-links
				// Temporary solution might be just ignoring it :-D

				// It's being addressed here:
				// http://bugs.sun.com/bugdatabase/view_bug.do;
				// jsessionid=2aad6f1bb61b1d33966563988a4?bug_id=4313887

				// Solution:
				// http://openjdk.java.net/projects/nio/javadoc/index.html

				// folder has been selected before - there is mapping in cache for 
				// it
				if (cache.containsKey(folder)) {
					// checkboxes are loaded from cache
					currentFolderCheckBoxes = cache.get(folder);
					// and displayed in folderList
					folderList.fillPreparedFolder(currentFolderCheckBoxes);
				}
				// folder is selected for the first time - it is not in the
				// cache
				else {
					// checkboxes are obtained from folderList
					currentFolderCheckBoxes = folderList.fillFolder(folder);
					// and are put in the cache
					cache.put(folder, new HashSet<FileCheckBoxModel>(
							currentFolderCheckBoxes));
					// here we need explicit retyping
					// Collection<FileCheckBoxModel> cannot be used as
					// Collection<BasicCheckBoxModel>
					Collection<BasicCheckBoxModel> jCheckboxes = 
						new HashSet<BasicCheckBoxModel>();
					for (FileCheckBoxModel fcb : currentFolderCheckBoxes) {
						jCheckboxes.add(fcb);
					}
					// retyped collection is set as slaves to the master
					// checkbox
					node.getFileCheckBox().setSlaves(jCheckboxes);
				}

				// Here we need to query the globalSelectionModel if the files
				// represented by these checkboxes are selected or not.
				// This must be done even when loading files from cache, because
				// the globaSelectionModel may be changed from outside.
				for (FileCheckBoxModel checkBox : currentFolderCheckBoxes) {
					if (globalSelectionModel.isFileSelected(checkBox.getFile())) {
						checkBox.setState(State.SELECTED);
					} else {
						checkBox.setState(State.NOT_SELECTED);
					}
				}
				// after setting selection for each checkbox, wee need to
				// repaint
				// the folderList
				folderList.repaint();
			}
		}
	}

	/**
	 * TreeCheckSelectionListener for the file tree.
	 */
	private class CheckSelectionListener implements TreeCheckSelectionListener 
	{  
		/**
		 * It will repaint the folder list if check selection changed. This
		 * ensures that the folder list gets repainted even when the currently
		 * selected folder is checked. 
		 */
		@Override
		public void treeCheckSelectionChanged(TreeCheckSelectionEvent event) {
			folderList.repaint();
		}
	}

	/**
	 * Mouse listener for the folder list.
	 */
	private class ListCheckMouseListener extends MouseAdapter 
	{
		/**
		 * This will check the fileCheckBox on the selected item in the folder 
		 * list.
		 */
		@Override
		public void mouseClicked(MouseEvent e) {
			int selectedIndex = folderList.locationToIndex(e.getPoint());
			if (selectedIndex < 0)
				return;

			//currently selected item in list
			FileCheckBoxModel checkbox = (FileCheckBoxModel) folderList
					.getModel().getElementAt(selectedIndex);

			//next state of checkbox
			checkbox.nextState();
			folderList.repaint();
			fileTree.repaint();

			//add/remove modified file from the selection model
			if (checkbox.isSelected()) {
				globalSelectionModel.addFile(checkbox.getFile());
			} else {
				globalSelectionModel.removeFile(checkbox.getFile());
			}
		}
	}

	/**
	 * Renderer for the file list.
	 */
	private class FileListCellRenderer extends JLabel implements ListCellRenderer
	{
		/**
		 * This will display files taken from the selection model as labels
		 * with file icon and file name with file path.
		 */
		@Override
		public Component getListCellRendererComponent(JList list, Object value,
				int index, boolean isSelected, boolean cellHasFocus) {
			//file to be painted
			File f = (File) value;
			//if its selected, it will get blue background 
			if (isSelected) {
				setOpaque(true);
				setBackground(new Color(127, 127, 255));
			} else {
				setOpaque(false);
			}
			//get icon for the file
			setIcon(FileUtil.getFileIcon(f));
			setText(f.getName() + " (" + f.getPath() + ")");
			return this;
		}
	}

	/**
	 * Listener for the Add button.
	 */
	private class AddFileButtonListener implements ActionListener 
	{	
		/**
		 * Shows a file chooser dialog and adds the selected file to the selection
		 * model.
		 */
		@Override
		public void actionPerformed(ActionEvent e) {
			JFileChooser chooser = new JFileChooser();
			File file;
			if (chooser.showOpenDialog(mainWindow) == JFileChooser.APPROVE_OPTION){
				file = chooser.getSelectedFile();
			}
			else{
				return;
			}
			
			//if the file is already selected, there is nothing else to do
			if (globalSelectionModel.isFileSelected(file)) {
				return;
			} else {
				//otherwise add the file to the model
				globalSelectionModel.addFile(file);
			}

			//then scroll in tree to the selected file 
			fileTree.scrollToFile(file);

			if (currentFolderCheckBoxes == null) {
				return;
			}
			//and check the corresponding file in the folder list
			for (FileCheckBoxModel cb : currentFolderCheckBoxes) {
				if (cb.getFile().equals(file)) {
					cb.setSelected(true);
					folderList.repaint();
					fileTree.repaint();
					return;
				}
			}
		}
	}

	/**
	 * Listener for the Remove button.
	 */
	private class RemoveFileButtonListener implements ActionListener 
	{
		/**
		 * Picks the selected file from the file list and removes it from the 
		 * selection model.
		 */
		@Override
		public void actionPerformed(ActionEvent e) {
			File f = (File) fileList.getSelectedValue();
			globalSelectionModel.removeFile(f);
			//scroll to file in the tree
			fileTree.scrollToFile(f);
			if (currentFolderCheckBoxes == null) {
				return;
			}
			//and uncheck the checkbox for the removed file in the folder list
			for (FileCheckBoxModel cb : currentFolderCheckBoxes) {
				if (cb.getFile().equals(f)) {
					cb.setSelected(false);
					folderList.repaint();
					fileTree.repaint();
					return;
				}
			}
		}
	}

	/**
	 * Listener for the Cancel button
	 */
	private class CancelListener implements ActionListener 
	{
		/**
		 * Shows the confirmation dialog and then closes the program.
		 */
		@Override
		public void actionPerformed(ActionEvent e) {
			int result = JOptionPane.showConfirmDialog(mainWindow,
					"Are you sure you want to close the program?");
			if (result == JOptionPane.YES_OPTION) {
				System.exit(0);
			}
		}
	}

	/**
	 * Observer for the number of files selected.
	 */
	private class FilesCountObserver implements FileSelectionObserver 
	{
		/**
		 * This will listen for changes in the selection model. It will update
		 * the files count label each time number of files changes. It also
		 * synchronizes the file list according to the input paremeters
		 */
		@Override
		public void update(Collection<File> files, boolean added) {
			filesCountLabel.setText("Number of files: "
					+ globalSelectionModel.getFilesCount());
			if (added) {
				for (File f : files) {
					fileListModel.addElement(f);
				}
			} 
			else {
				for (File f : files) {
					fileListModel.removeElement(f);
				}
			}
		}
	}
}
